#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <syslog.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "util.h"

#define SERVER_PORT 20815
#define LISTENQ     10
#define MAX_BUFF_SIZE 1024

typedef void (*server_process_func_t)(int, const struct sockaddr *, size_t);

void start_daemon(void);
void start_tcp_server(short servport, server_process_func_t serv_proc_fp);
void server_process(int connfd, const struct sockaddr *cliaddr, size_t cliaddr_len);
void sig_child_handler(int signo);

int main(int argc, char *argv[])
{
    start_daemon();
    start_tcp_server(SERVER_PORT, server_process);

    return 0;
}

void server_process(int connfd, const struct sockaddr *cliaddr, size_t cliaddr_len)
{
    char buff[MAX_BUFF_SIZE];
    int ret = 0;

    for (;;)
    {
        memset(buff, 0, sizeof(buff));
        if ((ret = get_string(connfd, buff, sizeof(buff) - 1)) < 0)
        {
            err_exit("get_string failed");
        }
        else if (0 == ret)
        {
            exit(0);
        }

        if ((put_string(connfd, buff, sizeof(buff) - 1)) < 0)
        {
            err_exit("put_string failed");
        }
    }
}

void start_daemon(void)
{
    struct rlimit rl;
    pid_t pid; 
    int fd0;
    int fd1;
    int fd2;
    int i;

    if (getrlimit(RLIMIT_NOFILE, &rl) != 0)
    {
        err_exit("getrlimit on NOFILE failed");
    }

    umask(0);

    if ((pid = fork()) < 0)
    {
        err_exit("daemon fork failed");
    }
    else if (pid != 0)
    {
        exit(0);
    }
    setsid();

    if ((pid = fork()) < 0)
    {
        err_exit("daemon fork failed");
    }
    else if (pid != 0)
    {
        exit(0);
    }

    if (chdir("/") != 0)
    {
        err_exit("chdir to root failed");
    }

    if (RLIM_INFINITY == rl.rlim_max)
    {
        rl.rlim_max = 1024;
    }
    for (i = 0; i < rl.rlim_max; ++i)
    {
        close(i);
    }

    fd0 = open("/dev/null", O_RDWR);
    fd1 = open("/tmp/server_info.log",
               O_RDWR | O_CREAT | O_APPEND,
               S_IRUSR | S_IWUSR | S_IRGRP);
    fd2 = open("/tmp/server_error.log",
               O_RDWR | O_CREAT | O_APPEND,
               S_IRUSR | S_IWUSR | S_IRGRP);

    openlog("[test_server]", LOG_CONS, LOG_DAEMON);
    if (fd0 != 0 || fd1 != 1 || fd2 != 2)
    {
        syslog(LOG_ERR, "unexpected file descriptors fd0=[%d], fd1=[%d], fd2=[%d]",
               fd0, fd1, fd2);
        exit(1);
    }
}

void start_tcp_server(short servport, server_process_func_t serv_proc_fp)
{
    struct sockaddr_in servaddr;
    struct sockaddr_in cliaddr;
    size_t cliaddr_len;
    int listenfd;
    int connfd;
    pid_t childpid;

    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        err_exit("socket failed");
    }

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = INADDR_ANY;
    servaddr.sin_port = htons(servport);

    if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0)
    {
        err_exit("bind failed");
    }

    if (listen(listenfd, LISTENQ) != 0)
    {
        err_exit("listen failed");
    }

    if (signal_intr(SIGCHLD, sig_child_handler) == SIG_ERR)
    {
        err_exit("set signal handler on SIGCHLD failed");  
    }

    for (;;)
    {
        cliaddr_len = sizeof(cliaddr);
        if ((connfd = accept(listenfd, (struct sockaddr *)&cliaddr,
                             &cliaddr_len)) == -1)
        {
            if (EINTR == errno || ECONNABORTED == errno)
            {
                continue;
            }
            else
            {
                err_exit("accept failed");
            }
        }

        if ((childpid = fork()) < 0)
        {
            err_exit("fork failed");
        }
        else if (0 == childpid)
        {
            close(listenfd);
            serv_proc_fp(connfd, (struct sockaddr *)&cliaddr, cliaddr_len);
            exit(0);
        }
        close(connfd);
    }
}

void sig_child_handler(int signo)
{
    pid_t pid;
    int stat;
    int errno_old = errno;

    while ((pid = waitpid(-1, &stat, WNOHANG)) > 0)
        ;

    errno = errno_old;
    return;
}
